package com.mgy.db.sharding.utils;

import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import com.mgy.db.TableInfo;
import com.mgy.db.sharding.druid.MyDruidDataSource;
import org.apache.shardingsphere.core.config.ShardingConfigurationException;
import org.apache.shardingsphere.core.util.InlineExpressionParser;
import org.apache.shardingsphere.core.yaml.config.sharding.YamlRootShardingConfiguration;
import org.apache.shardingsphere.core.yaml.config.sharding.YamlShardingRuleConfiguration;
import org.apache.shardingsphere.core.yaml.config.sharding.YamlTableRuleConfiguration;
import org.apache.shardingsphere.core.yaml.engine.YamlEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * yaml解析
 *
 * @author maguoyong
 * @date 2019/12/17
 */
public final class ShardingYamlUtil {
    private static final Logger logger = LoggerFactory.getLogger(ShardingYamlUtil.class);

    private static final String DELIMITER = ".";

    public static YamlRootShardingConfiguration parseYaml(InputStream inputStream) throws IOException {
        byte[] bytes = getBytes(inputStream);
        YamlRootShardingConfiguration config = YamlEngine.unmarshal(bytes, YamlRootShardingConfiguration.class);
        Map<String, DataSource> dataSourceMap = config.getDataSources();
        dataSourceMap.forEach((k, v) -> {
            MyDruidDataSource dataSource = (MyDruidDataSource) v;
            if (!StringUtils.hasText(dataSource.getPassword()) && StringUtils.hasText(dataSource.getCiphertext())) {
                String pwd = decode(dataSource.getCiphertext());
                dataSource.setPassword(pwd);
            }
        });

        return config;
    }

    public static byte[] getBytes(InputStream inputStream) throws IOException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int num;
            while ((num = inputStream.read(buffer)) != -1) {
                baos.write(buffer, 0, num);
            }
            baos.flush();
            return baos.toByteArray();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }


    public static String decode(String ciphertext) {
        //todo 解密
        return ciphertext;
    }

    public static void parseShardingRule(YamlRootShardingConfiguration configuration) {
        DbUtil.datasourceCount = configuration.getDataSources().size();
        YamlShardingRuleConfiguration yamlConfiguration = configuration.getShardingRule();
        for (YamlTableRuleConfiguration tableRuleConfiguration : yamlConfiguration.getTables().values()) {
            String logicTable = tableRuleConfiguration.getLogicTable().toLowerCase();
            List<String> dataNodes = new InlineExpressionParser(tableRuleConfiguration.getActualDataNodes()).splitAndEvaluate();
            Set<String> datasourceSet = Sets.newHashSet();
            Set<String> actualTableNameSet = Sets.newHashSet();
            if (CollectionUtils.isEmpty(dataNodes)) {
                actualTableNameSet.add(tableRuleConfiguration.getLogicTable());
                datasourceSet.addAll(configuration.getDataSources().keySet());
            }
            for (String dataNode : dataNodes) {
                if (!isValidDataNode(dataNode)) {
                    throw new ShardingConfigurationException("Invalid format for actual data nodes: '%s'", dataNode);
                }
                List<String> segments = Splitter.on(DELIMITER).splitToList(dataNode);
                String dataSourceName = segments.get(0);
                String tableName = segments.get(1);
                actualTableNameSet.add(tableName);
                datasourceSet.add(dataSourceName);
            }
            TableInfo tableInfo = new TableInfo(logicTable, datasourceSet.size(), actualTableNameSet.size());
            DbUtil.TABLES.put(logicTable, tableInfo);
        }
    }

    private static boolean isValidDataNode(final String dataNodeStr) {
        return dataNodeStr.contains(DELIMITER) && 2 == Splitter.on(DELIMITER).splitToList(dataNodeStr).size();
    }

    public static void modifyDatasource(YamlRootShardingConfiguration configuration, Boolean tableMetaDataInit) {
        if (tableMetaDataInit == null || tableMetaDataInit) {
            return;
        }
        if (configuration.getShardingRule() != null) {
            configuration.getShardingRule().setDefaultDataSourceName(null);
        }
        if (configuration.getDataSources().size() == 1) {
            MyDruidDataSource firstDataSource = (MyDruidDataSource) configuration.getDataSources().values().iterator().next();
            MyDruidDataSource dataSource = new MyDruidDataSource();
            dataSource.setDriverClassName(firstDataSource.getDriverClassName());
            dataSource.setUrl(firstDataSource.getUrl());
            dataSource.setUsername(firstDataSource.getUsername());
            dataSource.setCiphertext(firstDataSource.getCiphertext());
            dataSource.setMaxWait(1000);
            dataSource.setInitialSize(0);
            dataSource.setMaxActive(0);
            dataSource.setMinIdle(0);
            configuration.getDataSources().put("ds_temp", dataSource);
        }
    }

    private byte[] parseYamlContent(InputStream inputStream) throws IOException {
        InputStreamReader streamReader = new InputStreamReader(inputStream);
        BufferedReader reader = new BufferedReader(streamReader);
        String line;
        List<String> lineList = new ArrayList<>();
        String coordinate = null;
        String coordinateLine = null;
        String url = null;
        String userName = null;
        Integer retryTime = null;
        boolean isStart = false;
        boolean isEnd = false;
        boolean isStop = false;
        boolean hasPwd = false;
        while ((line = reader.readLine()) != null) {
            if (line.trim().startsWith("#") || "".equals(line.trim())) {
                continue;
            }
            if (line.startsWith("dataSources")) {
                isStart = true;
            }
            if (!"".equals(line.trim()) && !line.startsWith(" ") && !line.startsWith("dataSources")) {
                isEnd = true;
            }

            if (isStart && !isStop) {
                if (line.contains("!!") || isEnd) {
                    if (isEnd) {
                        isStop = true;
                    }
                    if (!hasPwd && coordinate != null && url != null && userName != null) {
                        //todo 替换配置文件的密码
                        String pwd = "***";
                        String pwdLine = coordinateLine.replace("coordinate:", "password:").replace(coordinate, pwd);
                        lineList.add(pwdLine);
                    }
                    coordinate = null;
                    url = null;
                    userName = null;
                    hasPwd = false;
                } else {
                    String temp = line.trim();
                    if (temp.matches("^coordinate:.*")) {
                        coordinate = temp.replace("coordinate:", "").trim();
                        coordinateLine = line;
                    } else if (temp.matches("^url:.*")) {
                        url = temp.replace("url:", "").trim();
                    } else if (temp.matches("^username:.*")) {
                        userName = temp.replace("username:", "").trim();
                    } else if (temp.matches("^retryTime:.*")) {
                        String retryTimeStr = temp.replace("retryTime:", "").trim();
                        if (retryTimeStr.length() > 0) {
                            retryTime = Integer.valueOf(retryTimeStr);
                        }
                    } else if (temp.matches("^password:.*")) {
                        hasPwd = true;
                    }
                }
            }
            lineList.add(line);
        }
        String content = String.join("\n", lineList);
        if (logger.isDebugEnabled()) {
            logger.debug(content);
        }
        return content.getBytes("utf-8");
    }

}
